import time
import asyncio
from scipy.signal import remez
from typing import List

import math
from bitstring import BitArray
from pydantic import conlist

from common.third_party.devmem import DevMem
from common.third_party.ni_types import FXP
from services.ad936x.api import Ad936xApi, DoubleFrequency
from services.ad936x.schemas.configs import SingleConfig
from services.control.api import ControlApi
from services.dsp.schemas.configs import DspConfig, Delays, \
    DspModesConfig, DspSubchannelsControl
from services.dsp.util import ExtractCoe
from common.third_party.save_config import save_config, load_config
from settings import settings


class Dsp:
    __slots__ = ['calibration_type',
                 'configs', 'coe_configs', 'coe_files', 'registers', 'test_mode',
                 'dsp_modes', 'subchannels_controls', 'do_reinit', 'offsets_lo']

    DEFAULT_SAMPLE_RATE = SingleConfig().sample_rate

    AD_API = Ad936xApi()
    CTRL_API = ControlApi()

    UPLINK_SV_RX_BASE_ADDR = 0x83C3_0000
    UPLINK_LINK_RX_BASE_ADDR = 0x83C4_0000
    UPLINK_BASE_ADDR = 0x83C5_0000
    DOWNLINK_BASE_ADDR = 0x83C6_0000
    SLV_REG0_ADDR = 0x83C1_0000
    SUBCHANNELS_CONTROL_SERVICE_ADDRS = [0x83CF_0000, 0x83D0_0000]

    CONFIG_VALUES = [-10.45, 0.25, 0xAA, -10.45]

    DELAY_OFFSET: list = [0x20, 0x22, 0x24, 0x26]

    MAX_VALUE = 0xFFFF_FFFF

    addresses = [
        0x83D6_0000, 0x83D4_0000, 0x83D5_0000, 0x83D3_0000,
        0x83D8_0000, 0x83D7_0000, 0x83DA_0000, 0x83D9_0000,
        0x83E3_0000, 0x83E2_0000
    ]
    base_address = [0x83E0_0000, 0x83E1_0000, 0x83E5_0000]
    fd = 60_000_000

    def __init__(self, configurate=False) -> None:
        self.registers: list = [0x0C, 0x10, 0x04, 0x08]
        self.calibration_type = ''

        self.test_mode: bool = False

        self.offsets_lo = [load_config(f'dsp/lo_{i}', float, 0.0) for i in range(10)]

        self.configs = {
            'LINK_UL_SCALE': {
                'config': load_config(
                    'dsp/dsp_config_LINK_UL_SCALE', DspConfig,
                    DspConfig(c=[1.0] * settings.DSP_NUMBER)
                ),
                'addr': self.UPLINK_LINK_RX_BASE_ADDR,
                'offset': 0x04
            },
            'LINK_DL_SCALE': {
                'config': load_config(
                    'dsp/dsp_config_LINK_DL_SCALE', DspConfig,
                    DspConfig(c=[1.0] * settings.DSP_NUMBER)
                ),
                'addr': self.DOWNLINK_BASE_ADDR,
                'offset': 0x48
            },
            'SERVICE_TX_SCALE': {
                'config': load_config(
                    'dsp/dsp_config_SERVICE_TX_SCALE', DspConfig,
                    DspConfig(c=[2.0] * settings.DSP_NUMBER)
                ),
                'addr': self.DOWNLINK_BASE_ADDR,
                'offset': 0x04
            },
            'SERVICE_RX_SCALE': {
                'config': load_config(
                    'dsp/dsp_config_SERVICE_RX_SCALE', DspConfig,
                    DspConfig(c=[1.0] * settings.DSP_NUMBER)
                ),
                'addr': self.UPLINK_SV_RX_BASE_ADDR,
                'offset': 0x04
            },
            'LINK_RTX_UL_OFFSET': {
                'config': load_config(
                    'dsp/dsp_config_LINK_RTX_UL_OFFSET', DspConfig,
                    DspConfig(c=[1.0] * settings.DSP_NUMBER)
                ),
                'offset_config': load_config(
                    'dsp/dsp_offset_config_LINK_RTX_UL_OFFSET', DspConfig,
                    DspConfig(c=[1.0] * settings.DSP_NUMBER)
                ),
                'addr': self.UPLINK_LINK_RX_BASE_ADDR,
                'offset': 0x08,
                'enable_offset': True
            },
            'LINK_RTX_DL_OFFSET': {
                'config': load_config(
                    'dsp/dsp_config_LINK_RTX_DL_OFFSET', DspConfig,
                    DspConfig(c=[1.0] * settings.DSP_NUMBER)
                ),
                'offset_config': load_config(
                    'dsp/dsp_offset_config_LINK_RTX_DL_OFFSET', DspConfig,
                    DspConfig(c=[1.0] * settings.DSP_NUMBER)
                ),
                'addr': self.DOWNLINK_BASE_ADDR,
                'offset': 0x38,
                'enable_offset': True
            },
            'SERVICE_TX_OFFSET': {
                'config': load_config(
                    'dsp/dsp_config_SERVICE_TX_OFFSET', DspConfig,
                    DspConfig(c=[1.0] * settings.DSP_NUMBER)
                ),
                'offset_config': load_config(
                    'dsp/dsp_offset_config_SERVICE_TX_OFFSET', DspConfig,
                    DspConfig(c=[1.0] * settings.DSP_NUMBER)
                ),
                'addr': self.DOWNLINK_BASE_ADDR,
                'offset': 0x28,
                'enable_offset': True
            },
            'SERVICE_RX_OFFSET': {
                'config': load_config(
                    'dsp/dsp_config_SERVICE_RX_OFFSET', DspConfig,
                    DspConfig(c=[1.0] * settings.DSP_NUMBER)
                ),
                'offset_config': load_config(
                    'dsp/dsp_offset_config_SERVICE_RX_OFFSET', DspConfig,
                    DspConfig(c=[1.0] * settings.DSP_NUMBER)
                ),
                'addr': self.UPLINK_SV_RX_BASE_ADDR,
                'offset': 0x08,
                'enable_offset': True
            },
            'LINK_RX_OUT_OFFSET': {
                'config': load_config(
                    'dsp/dsp_config_LINK_RX_OUT_OFFSET', DspConfig,
                    DspConfig(c=[1.0] * settings.DSP_NUMBER)
                ),
                'offset_config': load_config(
                    'dsp/dsp_offset_config_LINK_RX_OUT_OFFSET', DspConfig,
                    DspConfig(c=[1.0] * settings.DSP_NUMBER)
                ),
                'addr': self.UPLINK_BASE_ADDR,
                'offset': 0x04,
                'enable_offset': True
            },
            'LINK_TX_OUT_OFFSET': {
                'config': load_config(
                    'dsp/dsp_config_LINK_TX_OUT_OFFSET', DspConfig,
                    DspConfig(c=[1.0] * settings.DSP_NUMBER)
                ),
                'offset_config': load_config(
                    'dsp/dsp_offset_config_LINK_TX_OUT_OFFSET', DspConfig,
                    DspConfig(c=[1.0] * settings.DSP_NUMBER)
                ),
                'addr': self.DOWNLINK_BASE_ADDR,
                'offset': 0x08,
                'enable_offset': True
            }
        }
        self.coe_configs = {
            'COE_UL_LNK_LNK': {
                'addr': self.UPLINK_LINK_RX_BASE_ADDR, 'offset': 0x18,
                'loaded_coe-1': load_config('dsp/loaded_coe-1_COE_UL_LNK_LNK', str, '5MHz'),
                'loaded_coe-2': load_config('dsp/loaded_coe-2_COE_UL_LNK_LNK', str, '5MHz'),
                'loaded_coe-3': load_config('dsp/loaded_coe-3_COE_UL_LNK_LNK', str, '5MHz'),
                'loaded_coe-4': load_config('dsp/loaded_coe-4_COE_UL_LNK_LNK', str, '5MHz')
            },
            'COE_UL_SV_LNK': {
                'addr': self.UPLINK_SV_RX_BASE_ADDR, 'offset': 0x18,
                'loaded_coe-1': load_config('dsp/loaded_coe-1_COE_UL_SV_LNK', str, '5MHz'),
                'loaded_coe-2': load_config('dsp/loaded_coe-2_COE_UL_SV_LNK', str, '5MHz'),
                'loaded_coe-3': load_config('dsp/loaded_coe-3_COE_UL_SV_LNK', str, '5MHz'),
                'loaded_coe-4': load_config('dsp/loaded_coe-4_COE_UL_SV_LNK', str, '5MHz')
            },
            'COE_DL_LNK_SV': {
                'addr': self.DOWNLINK_BASE_ADDR, 'offset': 0x18,
                'loaded_coe-1': load_config('dsp/loaded_coe-1_COE_DL_LNK_SV', str, '5MHz'),
                'loaded_coe-2': load_config('dsp/loaded_coe-2_COE_DL_LNK_SV', str, '5MHz'),
                'loaded_coe-3': load_config('dsp/loaded_coe-3_COE_DL_LNK_SV', str, '5MHz'),
                'loaded_coe-4': load_config('dsp/loaded_coe-4_COE_DL_LNK_SV', str, '5MHz')
            },
        }

        dsp_path = f'{settings.APP_PATH}/services/dsp/coe_files/'
        self.coe_files = {
            '5MHz': ExtractCoe.extract_hex_coe_from_file(open(dsp_path + '5MHz_h.coe').read()),
            '10MHz': ExtractCoe.extract_hex_coe_from_file(open(dsp_path + '10MHz_h.coe').read()),
            '15MHz': ExtractCoe.extract_hex_coe_from_file(open(dsp_path + '15MHz_h.coe').read()),
            '20MHz': ExtractCoe.extract_hex_coe_from_file(open(dsp_path + '20MHz_h.coe').read()),
            '30MHz': ExtractCoe.extract_hex_coe_from_file(open(dsp_path + '30MHz_h.coe').read()),
        }

        self.dsp_modes = load_config('dsp/dsp_modes', DspModesConfig, DspModesConfig())

        self.subchannels_controls = [
            load_config('dsp/subchannels_controls_0', DspSubchannelsControl, DspSubchannelsControl()),
            load_config('dsp/subchannels_controls_1', DspSubchannelsControl, DspSubchannelsControl())
        ]

        if configurate:
            # TODO link pilot freq
            # В режиме FSC pilot-rx-0 отвечает только за приём, за передачу отвечает pilot-rx-1
            self.set_freq(enable=1, freq=-26_000_000, width=50)
            self.set_thresh(values=[5, 5, 4])
            self.set_cycles(15)
            self.write(0x83E7_0000, 5)

            for name in self.configs.keys():
                asyncio.create_task(self.configure(name, self.configs))

            for name in self.coe_configs.keys():
                for i in range(1, 5):
                    self.write_typical_coe_file(name, i, self.coe_configs[name].get(f'loaded_coe-{i}'))

            for i in range(2):
                asyncio.create_task(self.set_subchannels_control(i, self.subchannels_controls[i]))

            self.set_dsp_modes(self.dsp_modes)

            self.do_reinit = True

    def set_pilot_freq(self, index: int, freq_rx: int, freq_tx: int):
        base_address = self.base_address[index]
        freq_rx_code = int((4294967296 * freq_rx) / self.fd)
        freq_tx_code = int((4294967296 * freq_tx) / self.fd)
        self.write(base_address + 4 * 3, freq_rx_code)
        self.write(base_address + 4 * 12, freq_tx_code)

    def get_pilot_freq(self, index: int) -> tuple[int, int]:
        base_address = self.base_address[index]
        freq_rx_code = self.read(base_address + 4 * 3)
        freq_tx_code = self.read(base_address + 4 * 12)
        freq_rx = int((freq_rx_code * self.fd) / 4294967296)
        freq_tx = int((freq_tx_code * self.fd) / 4294967296)
        return freq_rx, freq_tx

    def set_simulation_of_chain(self, state: bool):
        self.bit_write(19, state)

    def get_simulation_of_chain(self) -> bool:
        return self.bit_read(19)

    @staticmethod
    def write(address: int, value: int):
        if not settings.DEBUG:
            mem = DevMem(address=address)
            mem.write(value=value)

    def set_freq(self, enable, freq=999450.6835937, width=30):
        if not settings.DEBUG:
            for base_address in self.base_address:
                if enable == 1:
                    freq_code = int((4294967296 * freq) / self.fd)
                    width_code = int((width * self.fd) / 1_000_000 - 2)

                    self.write(base_address + 4 * 3, freq_code)
                    self.write(base_address + 4 * 12, freq_code)

                    i = 4
                    address = base_address + 4 * i
                    self.write(address, width_code)

                i = 2
                address = base_address + 4 * i
                self.write(address, 4 * enable)

    # def set_cycles(self, value):
    #     for base_address in self.base_address:
    #         address = base_address + 4 * 5
    #         rdata = self.read(address)
    #         rdata = rdata & 0xFE1E_1FFF
    #         fp = value & 0xF
    #         tp = (value & 0xF0) >> 4
    #         data = rdata + fp * (1 << 13)
    #         data = data + tp * (1 << 21)
    #         self.write(address, data)

    def set_cycles(self, value):
        for base_address in self.base_address:
            address = base_address + 4 * 5
            rdata = self.read(address)
            rdata = rdata & 4294844415
            data = rdata + value * 8192
            self.write(address, data)

    def get_cycles(self):
        address = self.base_address[0] + 4 * 5
        rdata = self.read(address)
        rdata = (rdata & 0x1E_1E000) >> 13
        lo = rdata & 0xf
        ho = (rdata >> 4)
        return ho + lo



    @staticmethod
    def read(address: int, signed=False) -> int | None:
        if not settings.DEBUG:
            mem = DevMem(address=address)
            return mem.get_value()

    def set_thresh(self, values: list[int]):
        if not settings.DEBUG:
            for idx, base_address in enumerate(self.base_address):
                i = 5
                address = base_address + 4 * i
                rdata = self.read(address, True)
                rdata = rdata & 4293001215
                data = rdata + values[idx] * 131072
                self.write(address, data)

    @staticmethod
    def get_mem(address, ws=4) -> DevMem:
        return DevMem(address=address, number_of_words=256, word_size=ws)

    def write_typical_coe_file(self, name: str, coe_number: int, coe_name: str) -> None:
        save_config(f'dsp/loaded_coe-{coe_number}_{name}', coe_name)
        coe_configs = self.coe_configs
        addr = coe_configs.get(name).get('addr')
        offset = coe_configs.get(name).get('offset') + (coe_number - 1) * 2
        self._write_typical_coe(addr, offset, coe_name)

    def write_typical_coe_file_scipy(self, name: str, coe_number: int) -> None:
        coe_configs = self.coe_configs
        addr = coe_configs.get(name).get('addr')
        offset = coe_configs.get(name).get('offset') + (coe_number - 1) * 2
        self._write_typical_coe_scipy(addr, offset)

    def write_custom_hex_coe_file(self, name: str, coe_number: int, offset_bw_bands: int) -> None:
        if not settings.DEBUG:
            addr = self.coe_configs.get(name).get('addr')
            offset = self.coe_configs.get(name).get('offset') + (coe_number - 1) * 2

            self._write_custom_hex_coe(addr, offset, [], padding=0)
            self.coe_configs[name][f'loaded_coe-{coe_number}'] = 'CUSTOM'
            save_config(f'dsp/loaded_coe-{coe_number}_{name}', 'CUSTOM')

    def write_custom_coe_file(self, name: int, coe_number: int, value: List[int]) -> None:
        if not settings.DEBUG:
            addr = self.coe_configs.get(str(name)).get('addr')
            offset = self.coe_configs.get(str(name)).get('offset') + (coe_number - 1) * 2

            self._write_custom_coe(addr, offset, value)
            self.coe_configs[name][f'loaded_coe-{coe_number}'] = 'CUSTOM'

    @staticmethod
    def reset_coe_controller(addr: int, offset: int) -> None:
        mem = DevMem(address=addr, number_of_words=32, word_size=2)
        mem.write(offset=offset, value=0x10)

    async def configure(self, name: str, configs: dict, do_reinit=True, config: DspConfig=None) -> None:
        save_config(f'dsp/dsp_config_{name}', configs.get(name).get('config'))
        if 'SCALE' in name:
            addr = configs.get(name).get('addr')
            offset = configs.get(name).get('offset')
            if do_reinit or config is None:
                config = configs.get(name).get('config')
            self._write_scale(addr, offset, config)
        elif 'OFFSET' in name:
            addr = configs.get(name).get('addr')
            offset = configs.get(name).get('offset')
            if do_reinit or config is None:
                config = configs.get(name).get('config')
            await self._write_offset(addr, offset, config)
        elif 'PILOT' in name:
            addr = configs.get(name).get('addr')
            offset = configs.get(name).get('offset')
            if do_reinit or config is None:
                config = configs.get(name).get('config')
            await self._write_offset_pilot(addr, offset, config)

    def set_dsp_modes(self, modes: DspModesConfig):
        self.dsp_modes = modes
        save_config('dsp/dsp_modes', self.dsp_modes)

        if not settings.DEBUG:
            mem = self.get_mem(address=Dsp.SLV_REG0_ADDR)
            mem[21] = modes.service_4_on_nb_Ch
            mem[22] = modes.service_3_on_nb_Ch
            mem[23] = modes.service_4_on_without_add_filter
            mem[24] = modes.service_3_on_without_add_filter
            mem[25] = modes.inj_dat_from2ch_to_serv3_4
            mem[26] = modes.serv1_to_serv1_and_serv2
            mem[27] = modes.ul_on_nb_off_wb
            mem[28] = modes.ul_mode_on_add_off_std
            mem[29] = modes.ul_sum_1_and_2_channel

    def get_dsp_modes(self) -> DspModesConfig:
        if not settings.DEBUG:
            mem = self.get_mem(address=Dsp.SLV_REG0_ADDR)
            self.dsp_modes.service_4_on_nb_Ch = mem[21]
            self.dsp_modes.service_3_on_nb_Ch = mem[22]
            self.dsp_modes.service_4_on_without_add_filter = mem[23]
            self.dsp_modes.service_3_on_without_add_filter = mem[24]
            self.dsp_modes.inj_dat_from2ch_to_serv3_4 = mem[25]
            self.dsp_modes.serv1_to_serv1_and_serv2 = mem[26]
            self.dsp_modes.ul_on_nb_off_wb = mem[27]
            self.dsp_modes.ul_mode_on_add_off_std = mem[28]
            self.dsp_modes.ul_sum_1_and_2_channel = mem[29]

        return self.dsp_modes

    async def set_subchannels_control(self, index: int, control: DspSubchannelsControl):
        self.subchannels_controls[index] = control
        save_config(f'dsp/subchannels_controls_{index}', control)

        if not settings.DEBUG:
            base_addr = Dsp.SUBCHANNELS_CONTROL_SERVICE_ADDRS[index]
            self.get_mem(address=base_addr + 0x14, ws=1).write(value=int(control.nb_subchannels_control_service))
            await self._write_offsets(base_addr + 0x4, control.subchannels[0].offset_in)
            await self._write_offsets(base_addr + 0x8, control.subchannels[0].offset_out)
            await self._write_offsets(base_addr + 0xC, control.subchannels[1].offset_in)
            await self._write_offsets(base_addr + 0x10, control.subchannels[1].offset_out)
            if index == 0:
                await self._write_offsets(0x83D3_0000, control.offset_nb_bw)
                await self._write_offsets(0x83D4_0000, control.offset_wb_bw)
            elif index == 1:
                await self._write_offsets(0x83D5_0000, control.offset_nb_bw)
                await self._write_offsets(0x83D6_0000, control.offset_wb_bw)

    def get_subchannels_control(self, index: int) -> DspSubchannelsControl:
        return self.subchannels_controls[index]

    async def _write_offsets(self, addr: int, offset: float, sample_rate_multiplier: float = 1.0):
        if not settings.DEBUG:
            try:
                sample_rate = (await self.AD_API.get_config(0)).sample_rate
                if not isinstance(sample_rate, int):
                    raise TypeError
            except Exception as e:
                #print(e)
                sample_rate = self.DEFAULT_SAMPLE_RATE

            sample_rate *= sample_rate_multiplier

            mem = self.get_mem(address=addr)
            mem.write(offset=0, value=round((offset * 1_000_000) * (2 ** 30) / sample_rate))
        else:
            pass

    def _write_scale(self, addr: int, offset: int, config: DspConfig) -> None:
        if not settings.DEBUG:
            mem = self.get_mem(address=addr, ws=1)
            for i, value in enumerate(config.c):
                mem.write(offset=offset + i, value=value)
        else:
            pass

    async def _write_offset_pilot(self, addr: int, offset: int, config: DspConfig) -> None:
        if not settings.DEBUG:
            try:
                sample_rate = (await self.AD_API.get_config(0)).sample_rate
                if not isinstance(sample_rate, int):
                    raise TypeError
            except Exception as e:
                #print(e)
                sample_rate = self.DEFAULT_SAMPLE_RATE

            delta_theta = [round((config.c[i] * 1_000_000) * (2 ** 30) / sample_rate) for i in
                           range(settings.DSP_NUMBER)]

            delta_theta[2] = config.c[2]

            mem = self.get_mem(address=addr)

            for i, value in enumerate([0x08, 0x0C, 0x04, 0x10]):
                mem.write(offset=offset + value, value=delta_theta[i])

            mem.write(offset=0x14, value=128)
        else:
            pass

    async def _write_offset(self, addr: int, offset: int, config: DspConfig) -> None:
        if not settings.DEBUG:
            try:
                sample_rate = (await self.AD_API.get_config(index=0)).sample_rate
                if not isinstance(sample_rate, int):
                    raise TypeError
            except Exception as e:
                #print(e)
                sample_rate = self.DEFAULT_SAMPLE_RATE

            delta_theta = [round((max(min(config.c[i], 100), -100) * 1_000_000) * (2
                                                              ** 30) / sample_rate) for i in
                           range(settings.DSP_NUMBER)]

            mem = self.get_mem(address=addr)
            for i, value in enumerate(range(0, settings.DSP_NUMBER * 4, 4)):
                mem.write(offset=offset + value, value=int(delta_theta[i]))
        else:
            pass

    @staticmethod
    def convert(x: float) -> int:
        return (int(x * 0x35_000) + 0x10_000) % 0x10_000

    @staticmethod
    def filter_configuration(number_of_channel, offset_bw_bands, fs):
        trans_width = 200000  # Width of transition from pass band to stop band, Hz
        bandpass_width = 200000  # Bandpass width, Hz
        band = [
            (offset_bw_bands / 2) - (bandpass_width / 2) + 3500,
            (offset_bw_bands / 2) + (bandpass_width / 2) + 3500
        ]  # Desired pass band, Hz. 3500 is used for cancel the Gibbs effect.

        if number_of_channel == 1:
            edges = [0, bandpass_width, bandpass_width + trans_width, 0.5 * fs]
            desired = [1, 0]
        if number_of_channel == 2:
            edges = [
                0, band[0] - trans_width, band[0], band[1], band[1] + trans_width,
                   0.5 * fs
            ]
            desired = [0, 1, 0]
        return edges, desired

    @staticmethod
    def bit_write(bit_number, value, address=0x83C1_0000):
        mem = DevMem(address=address, number_of_words=32, filename='/dev/mem', word_size=4)
        mem[bit_number] = value

    @staticmethod
    def bit_read(bit_number, address=0x83C1_0000):
        mem = DevMem(address=address, number_of_words=32, filename='/dev/mem', word_size=4)
        return mem[bit_number]

    async def write_subchannels_offsets_and_filters_crime(self, channel_2, channel_3, mode, max_arfsn, min_arfsn, service_2_state, service_3_state):
        block_state = await self.CTRL_API.get_control_state('block_state')
        await self.write_mode_of_channels(mode, channel_2.enabled, channel_3.enabled, service_2_state, service_3_state)

        is_gsm_mode = mode == 'GSM'
        if mode == 'LTE':
            return

        subchannels_offsets_and_filters = await self.calc_adfreq_subch_offsets_for_10ch(
            channel_2.channels[0], channel_2.channels[1], channel_2.channels[2], channel_2.channels[3],
            channel_2.erfsn,
            channel_3.channels[0], channel_3.channels[1], channel_3.channels[2], channel_3.channels[3],
            channel_3.erfsn,
            mode,
            max_arfsn, min_arfsn
        )

        tx_frq, rx_frq = subchannels_offsets_and_filters[0]
        if block_state:
            await self.AD_API.set_center_frequency(2, DoubleFrequency(tx_frequency=rx_frq, rx_frequency=tx_frq))
        else:
            await self.AD_API.set_center_frequency(2, DoubleFrequency(tx_frequency=tx_frq, rx_frequency=rx_frq))

        # (dl, ul)
        addresses = [
            (0x83CF_0000, 0x83D1_0000),
            (0x83CF_0002, 0x83D1_0002),
            (0x83D0_0000, 0x83D2_0000),
            (0x83D0_0002, 0x83D2_0002)
        ]
        modes = subchannels_offsets_and_filters[1]
        offsets_bw_bands = subchannels_offsets_and_filters[2]
        for addr, mode, bw_bands in zip(addresses, modes, offsets_bw_bands):
            self._write_custom_hex_coe_scipy(addr[0], 0, 0, mode, bw_bands)
            self._write_custom_hex_coe_scipy(addr[1], 0, 0, mode, bw_bands)

        # (in(dl, ul), out(dl, ul))
        addresses = [
            ((0x83CF_0004, 0x83D1_0004), (0x83CF_000C, 0x83D1_000C)),
            ((0x83CF_0008, 0x83D1_0008), (0x83CF_0010, 0x83D1_0010)),
            ((0x83D0_0004, 0x83D2_0004), (0x83D0_000C, 0x83D2_000C)),
            ((0x83D0_0008, 0x83D2_0008), (0x83D0_0010, 0x83D2_0010))
        ]
        offsets = subchannels_offsets_and_filters[3]
        for addr, value in zip(addresses, offsets):
            await self._write_offsets(addr[0][0], value[0] / 1_000_000.0, 1.0 / 3.0) # TODO /5
            await self._write_offsets(addr[0][1], value[0] / 1_000_000.0, 1.0 / 3.0)
            await self._write_offsets(addr[1][0], value[1] / 1_000_000.0, 1.0 / 3.0)
            await self._write_offsets(addr[1][1], value[1] / 1_000_000.0, 1.0 / 3.0)

        self.offsets_lo = subchannels_offsets_and_filters[4]
        for i, lo in enumerate(self.offsets_lo):
            save_config(f'dsp/lo_{i}', lo)
        for addr, value in zip(self.addresses, self.offsets_lo):
            await self._write_offsets(addr, value / 1_000_000.0)

        config: DspConfig = self.configs['SERVICE_RX_SCALE']['config']
        if not block_state and is_gsm_mode:
            config.c[2] = 0
            config.c[3] = 0

        self.configs['SERVICE_RX_SCALE']['config'] = config
        await self.configure('SERVICE_RX_SCALE', self.configs)
        self.do_reinit = True

    async def write_mode_of_channels(self, mode: str, channel_2_enabled, channel_3_enabled, service_2_state, service_3_state):
        block_state = await self.CTRL_API.get_control_state('block_state')
        mem = DevMem(address=0x83E4_0000, word_size=2)

        if mode == 'LTE':
            self.bit_write(23, 1)  # service_4_on_without_add_filter__23b
            self.bit_write(24, 1)  # service_3_on_without_add_filter__24b
            self.bit_write(22, 0)  # service_3_on_NB_Ch__22b
            self.bit_write(21, 0)  # service_4_on_NB_Ch__21b
            self.bit_write(25, 0)  # inj_dat_from2ch_to_serv3_4__25b
            self.bit_write(26, 0)  # serv1_to_serv1_and_serv2__26b
            self.bit_write(27, 0)  # ul_on_NB_off_WB__27b
            self.bit_write(28, 0)  # ul_mode_on_add_off_std__28b
            self.bit_write(29, 0)  # ul_sum_1_and_2_channel__29b
            self.bit_write(30, 0)  # inj_dat_from2ch_to_serv3
            self.bit_write(31, 0)  # inj_dat_from2ch_to_serv4
            self.bit_write(0, 0, address=0x83C6_0000)
            mem.write(0, 0x101)

        if mode == 'GSM':
            if block_state:
                self.bit_write(21, 0)  # service_4_on_NB_Ch__21b
                self.bit_write(22, 0)  # service_3_on_NB_Ch__22b_(not used)
                self.bit_write(23, 1)  # service_4_on_without_add_filter__23b
                self.bit_write(24, 1)  # service_3_on_without_add_filter__24b_(not used)
                self.bit_write(27, 0)  # ul_on_NB_off_WB__27b
                self.bit_write(28, 0)  # ul_mode_on_add_off_std__28b
                self.bit_write(0, 0, address=0x83C6_0000)
            else:
                self.bit_write(21, 1)  # service_4_on_NB_Ch__21b
                self.bit_write(22, 1)  # service_3_on_NB_Ch__22b
                self.bit_write(23, 0)  # service_4_on_without_add_filter__23b
                self.bit_write(24, 0)  # service_3_on_without_add_filter__24b
                self.bit_write(27, 1)  # ul_on_NB_off_WB__27b
                self.bit_write(28, 1)  # ul_mode_on_add_off_std__28b
                self.bit_write(0, 1, address=0x83C6_0000)
                DevMem(address=0x83C3_0007, word_size=1).write(0, 0)

            self.bit_write(25, 0)  # inj_dat_from2ch_to_serv3_4__25b
            self.bit_write(26, 0)  # serv1_to_serv1_and_serv2__26b
            self.bit_write(29, 0)  # ul_sum_1_and_2_channel__29b
            self.bit_write(30, 0)  # inj_dat_from2ch_to_serv3
            self.bit_write(31, 0)  # inj_dat_from2ch_to_serv4

            mem.write(0, 0x02)

        if mode == 'LTE_GSM':
            if block_state:
                self.bit_write(23, 1)  # service_4_on_without_add_filter__23b
                self.bit_write(24, 1)  # service_3_on_without_add_filter__24b
                self.bit_write(26, 0)  # serv1_to_serv1_and_serv2__26b
                self.bit_write(29, 0)  # ul_sum_1_and_2_channel__29b
                self.bit_write(28, 0)  # ul_mode_on_add_off_std__28b
                self.bit_write(30, 0)  # inj_dat_from2ch_to_serv3
                self.bit_write(31, 0)  # inj_dat_from2ch_to_serv4
                self.bit_write(0, 0, address=0x83C6_0000)

            else:
                self.bit_write(23, 0)  # service_4_on_without_add_filter__23b
                self.bit_write(24, 0)  # service_3_on_without_add_filter__24b
                self.bit_write(26, 1)  # serv1_to_serv1_and_serv2__26b
                self.bit_write(29, 1)  # ul_sum_1_and_2_channel__29b
                self.bit_write(28, 1)  # ul_mode_on_add_off_std__28b
                self.bit_write(30, channel_2_enabled)  # inj_dat_from2ch_to_serv3
                self.bit_write(31, channel_3_enabled)  # inj_dat_from2ch_to_serv4
                self.bit_write(0, 1, address=0x83C6_0000)

                self.configs['SERVICE_TX_SCALE']['config'].c[1] = 1
                await self.configure('SERVICE_TX_SCALE', self.configs)

            self.bit_write(27, 0)  # ul_on_NB_off_WB__27b
            self.bit_write(21, 0)  # service_4_on_NB_Ch__21b
            self.bit_write(22, 0)  # service_3_on_NB_Ch__22b
            self.bit_write(25, 1)  # inj_dat_from2ch_to_serv3_4__25b
            mem.write(0, 0x02)

            # if block_state:
            #     self.write_typical_coe_file('COE_DL_LNK_SV', 1, '20MHz')
            #     self.write_typical_coe_file('COE_DL_LNK_SV', 4, '30MHz')

    def calc_mode_of_fir_GSM(self, subch0, subch1, subch2, subch3):
        # Calculation number of nonzero GSM channels
        number_of_GSM_subchanels = 0
        for element in [subch0, subch1, subch2, subch3]:
            if element != 0:
                number_of_GSM_subchanels = number_of_GSM_subchanels + 1

        if number_of_GSM_subchanels == 4:
            mode_of_first_filter = 2
            mode_of_second_filter = 2

        elif number_of_GSM_subchanels == 3:
            mode_of_first_filter = 2
            mode_of_second_filter = 1

        elif number_of_GSM_subchanels == 2:
            mode_of_first_filter = 2
            mode_of_second_filter = 0

        elif number_of_GSM_subchanels == 1:
            mode_of_first_filter = 1
            mode_of_second_filter = 0

        else:
            mode_of_first_filter = 0
            mode_of_second_filter = 0

        return mode_of_first_filter, mode_of_second_filter

    def frequency_range_selection(self, subch):
        # Frequency range selection
        # GSM. Frequency step 200 kHz
        # EARFCN number subch4_lte, subch9_lte
        # ARFCN number subch0_gsm....subch8_gsm
        if (1 <= subch <= 124):
            DL_frequency = 935_200_000 + ((subch - 1) * 200000)
            UL_frequency = 890_200_000 + ((subch - 1) * 200000)

        elif (512 <= subch <= 885):
            DL_frequency = 1_805_200_000 + ((subch - 512) * 200000)
            UL_frequency = 1_710_200_000 + ((subch - 512) * 200000)

        elif (940 <= subch <= 1023):
            DL_frequency = 918_200_000 + ((subch - 940) * 200000)
            UL_frequency = 873_200_000 + ((subch - 940) * 200000)

        # LTE. Frequency step 100 kHz
        elif (1200 <= subch <= 1949):
            DL_frequency = 1_805_000_000 + ((subch - 1200) * 100000)
            UL_frequency = 1_710_000_000 + ((subch - 1200) * 100000)

        else:
            DL_frequency = 0
            UL_frequency = 0

        return DL_frequency, UL_frequency

    async def calc_adfreq_subch_offsets_for_10ch(self, subch0_gsm, subch1_gsm, subch2_gsm, subch3_gsm, subch4_lte, subch5_gsm,
                                           subch6_gsm, subch7_gsm, subch8_gsm, subch9_lte,
                                           mode_of_channels,
                                           max_arfsn, min_arfsn):
        def filter_zeros(in_num):
            return in_num != 0
        block_state = await self.CTRL_API.get_control_state('block_state')


        # Calculate AD9361 TX and RX frequencies
        freq_DL_0subch, freq_UL_0subch = self.frequency_range_selection(subch0_gsm)
        freq_DL_1subch, freq_UL_1subch = self.frequency_range_selection(subch1_gsm)
        freq_DL_2subch, freq_UL_2subch = self.frequency_range_selection(subch2_gsm)
        freq_DL_3subch, freq_UL_3subch = self.frequency_range_selection(subch3_gsm)
        freq_DL_4subch, freq_UL_4subch = self.frequency_range_selection(subch4_lte)
        freq_DL_5subch, freq_UL_5subch = self.frequency_range_selection(subch5_gsm)
        freq_DL_6subch, freq_UL_6subch = self.frequency_range_selection(subch6_gsm)
        freq_DL_7subch, freq_UL_7subch = self.frequency_range_selection(subch7_gsm)
        freq_DL_8subch, freq_UL_8subch = self.frequency_range_selection(subch8_gsm)
        freq_DL_9subch, freq_UL_9subch = self.frequency_range_selection(subch9_lte)

        frq_list_DL = [freq_DL_0subch, freq_DL_1subch, freq_DL_2subch, freq_DL_3subch, freq_DL_5subch,
                       freq_DL_6subch, freq_DL_7subch, freq_DL_8subch]
        frq_list_UL = [freq_UL_0subch, freq_UL_1subch, freq_UL_2subch, freq_UL_3subch, freq_UL_5subch,
                       freq_UL_6subch, freq_UL_7subch, freq_UL_8subch]

        frq_list_DL = list(filter(filter_zeros, frq_list_DL))
        frq_list_UL = list(filter(filter_zeros, frq_list_UL))

        if block_state:
            frq_max_DL = max(frq_list_DL)
            frq_min_DL = min(frq_list_DL)

            frq_max_UL = max(frq_list_UL)
            frq_min_UL = min(frq_list_UL)
        else:
            frq_max_DL, frq_max_UL = self.frequency_range_selection(max_arfsn)
            frq_min_DL, frq_min_UL = self.frequency_range_selection(min_arfsn)



        servTXfrq = (frq_max_DL - frq_min_DL) / 2 + frq_min_DL  # Central frequency for AD9361
        servRXfrq = (frq_max_UL - frq_min_UL) / 2 + frq_min_UL  # Central frequency for AD9361

        # DOWNLINK
        # Calculate mode for every FIR. For GSM filters in service downlink.
        mode_of_first_filter, mode_of_second_filter = self.calc_mode_of_fir_GSM(subch0_gsm, subch1_gsm, subch2_gsm,
                                                                                subch3_gsm)
        mode_of_third_filter, mode_of_fourth_filter = self.calc_mode_of_fir_GSM(subch5_gsm, subch6_gsm, subch7_gsm,
                                                                                subch8_gsm)

        # Calculate offset between bands for every filter
        if mode_of_first_filter == 2:
            offset_bw_bands_filter_1 = abs(freq_DL_0subch - freq_DL_1subch)
        else:
            offset_bw_bands_filter_1 = 0

        if mode_of_second_filter == 2:
            offset_bw_bands_filter_2 = abs(freq_DL_2subch - freq_DL_3subch)
        else:
            offset_bw_bands_filter_2 = 0

        if mode_of_third_filter == 2:
            offset_bw_bands_filter_3 = abs(freq_DL_5subch - freq_DL_6subch)
        else:
            offset_bw_bands_filter_3 = 0

        if mode_of_fourth_filter == 2:
            offset_bw_bands_filter_4 = abs(freq_DL_7subch - freq_DL_8subch)
        else:
            offset_bw_bands_filter_4 = 0

        # Calculate input offset before FIR and output offset after FIR
        if mode_of_first_filter == 2:
            offset_first_fil_in = servTXfrq - (offset_bw_bands_filter_1 / 2 + min(freq_DL_0subch, freq_DL_1subch))
        elif mode_of_first_filter == 1:
            offset_first_fil_in = servTXfrq - max(freq_DL_0subch, freq_DL_1subch)
        else:
            offset_first_fil_in = 0

        offset_first_fil_out = -offset_first_fil_in  # De_offset_MHz

        if mode_of_second_filter == 2:
            offset_second_fil_in = servTXfrq - (offset_bw_bands_filter_2 / 2 + min(freq_DL_2subch, freq_DL_3subch))
        elif mode_of_second_filter == 1:
            offset_second_fil_in = servTXfrq - max(freq_DL_2subch, freq_DL_3subch)
        else:
            offset_second_fil_in = 0

        offset_second_fil_out = -offset_second_fil_in  # De_offset_MHz

        if mode_of_third_filter == 2:
            offset_third_fil_in = servTXfrq - (offset_bw_bands_filter_3 / 2 + min(freq_DL_5subch, freq_DL_6subch))
        elif mode_of_third_filter == 1:
            offset_third_fil_in = servTXfrq - max(freq_DL_5subch, freq_DL_6subch)
        else:
            offset_third_fil_in = 0

        offset_third_fil_out = -offset_third_fil_in  # De_offset_MHz

        if mode_of_fourth_filter == 2:
            offset_fourth_fil_in = servTXfrq - (offset_bw_bands_filter_4 / 2 + min(freq_DL_7subch, freq_DL_8subch))
        elif mode_of_fourth_filter == 1:
            offset_fourth_fil_in = servTXfrq - max(freq_DL_7subch, freq_DL_8subch)
        else:
            offset_fourth_fil_in = 0

        offset_fourth_fil_out = -offset_fourth_fil_in  # De_offset_MHz

        def calculate_another_frq_ad9361(serv_frq, freq_4subch, freq_9subch):
            list_of_DL_frq = [serv_frq, freq_4subch, freq_9subch]
            list_of_DL_frq = list(filter(filter_zeros, list_of_DL_frq))
            frq_max_DL = max(list_of_DL_frq)
            frq_min_DL = min(list_of_DL_frq)
            serv_frq_add_mode = (frq_max_DL - frq_min_DL) / 2 + frq_min_DL
            return serv_frq_add_mode

        # Calculate additional offsets for LTE and GSM bands in LTE_GSM mode
        servTXfrq_add_mode = calculate_another_frq_ad9361(servTXfrq, freq_DL_4subch, freq_DL_9subch)
        servRXfrq_add_mode = calculate_another_frq_ad9361(servRXfrq, freq_UL_4subch, freq_UL_9subch)
        if (freq_DL_4subch and freq_DL_9subch) != 0:

            offset_lo_3_3 = freq_DL_4subch - servTXfrq_add_mode  # LTE Band 3 service
            offset_lo_2_3 = servTXfrq - servTXfrq_add_mode  # GSM Band 3 service

            offset_lo_3_4 = freq_DL_9subch - servTXfrq_add_mode  # LTE Band 4 service
            offset_lo_2_4 = servTXfrq - servTXfrq_add_mode  # GSM Band 4 service

            servTXfrq = servTXfrq_add_mode
            servRXfrq = servRXfrq_add_mode

            offset_lo_0_4 = -offset_lo_2_4
            offset_lo_0_3 = -offset_lo_2_3
            offset_lo_1_4 = offset_lo_2_4
            offset_lo_1_3 = offset_lo_2_3


            offset_lo_11_4 = offset_lo_3_4
            offset_lo_11_3 = offset_lo_3_3

            if not block_state:
                self.configs['SERVICE_RX_OFFSET']['config'].c[3] = -offset_lo_3_4 * 1 / 1_000_000

            self.configs['SERVICE_RX_OFFSET']['config'].c[2] = -offset_lo_3_3 * 1 / 1_000_000
            await self.configure('SERVICE_RX_OFFSET', self.configs)
            self.configs['LINK_TX_OUT_OFFSET']['config'].c[1] += -offset_lo_3_3 * 1 / 1_000_000
            await self.configure('LINK_TX_OUT_OFFSET', self.configs)
            self.configs['SERVICE_TX_OFFSET']['config'].c[2] = -offset_lo_2_3 * 1 / 1_000_000
            self.configs['SERVICE_TX_OFFSET']['config'].c[3] = -offset_lo_2_4 * 1 / 1_000_000
            await self.configure('SERVICE_TX_OFFSET', self.configs)

            self.do_reinit = True

        elif freq_DL_4subch != 0:

            offset_lo_3_3 = freq_DL_4subch - servTXfrq_add_mode  # LTE Band
            offset_lo_2_3 = servTXfrq - servTXfrq_add_mode  # GSM Band
            offset_lo_3_4 = 0
            offset_lo_2_4 = 0

            servTXfrq = servTXfrq_add_mode
            servRXfrq = servRXfrq_add_mode

            offset_lo_0_4 = -offset_lo_2_4
            offset_lo_0_3 = -offset_lo_2_3
            offset_lo_1_4 = offset_lo_2_4
            offset_lo_1_3 = offset_lo_2_3

            offset_lo_11_4 = 0
            offset_lo_11_3 = offset_lo_3_3

            self.configs['SERVICE_RX_OFFSET']['config'].c[2] = -offset_lo_3_3 * 1 / 1_000_000
            await self.configure('SERVICE_RX_OFFSET', self.configs)
            self.configs['LINK_TX_OUT_OFFSET']['config'].c[1] += -offset_lo_3_3 * 1 / 1_000_000
            await self.configure('LINK_TX_OUT_OFFSET', self.configs)
            self.configs['SERVICE_TX_OFFSET']['config'].c[2] = -offset_lo_2_3 * 1 / 1_000_000
            await self.configure('SERVICE_TX_OFFSET', self.configs)

            self.do_reinit = True

        elif freq_DL_9subch != 0:

            offset_lo_3_4 = freq_DL_9subch - servTXfrq_add_mode  # LTE Band
            offset_lo_2_4 = servTXfrq - servTXfrq_add_mode  # GSM Band
            offset_lo_3_3 = 0
            offset_lo_2_3 = 0

            servTXfrq = servTXfrq_add_mode
            servRXfrq = servRXfrq_add_mode

            offset_lo_0_4 = -offset_lo_2_4
            offset_lo_0_3 = -offset_lo_2_3
            offset_lo_1_4 = offset_lo_2_4
            offset_lo_1_3 = offset_lo_2_3

            offset_lo_11_4 = offset_lo_3_4
            offset_lo_11_3 = 0

            if not block_state:
                self.configs['SERVICE_RX_OFFSET']['config'].c[3] = -offset_lo_3_4 * 1 / 1_000_000

            await self.configure('SERVICE_RX_OFFSET', self.configs)
            self.configs['LINK_TX_OUT_OFFSET']['config'].c[1] += -offset_lo_3_4 * 1 / 1_000_000
            await self.configure('LINK_TX_OUT_OFFSET', self.configs)
            self.configs['SERVICE_TX_OFFSET']['config'].c[3] = -offset_lo_2_4 * 1 / 1_000_000
            await self.configure('SERVICE_TX_OFFSET', self.configs)

            self.do_reinit = True

        else:
            # Here are additional offsets only
            offset_lo_3_4 = 0  # Zero offsets downlink
            offset_lo_2_4 = 0  # Zero offsets downlink
            offset_lo_3_3 = 0  # Zero offsets downlink
            offset_lo_2_3 = 0  # Zero offsets downlink

            offset_lo_0_4 = 0  # Zero offsets uplink
            offset_lo_0_3 = 0  # Zero offsets uplink
            offset_lo_1_4 = 0  # Zero offsets uplink
            offset_lo_1_3 = 0  # Zero offsets uplink

            offset_lo_11_4 = 0  # Zero offsets uplink
            offset_lo_11_3 = 0  # Zero offsets uplink

        if block_state and mode_of_channels == 'LTE_GSM':
            offset_lo_11_4 = 0
            offset_lo_11_3 = 0

        return (
            (servTXfrq, servRXfrq),
            [mode_of_first_filter, mode_of_second_filter, mode_of_third_filter, mode_of_fourth_filter],
            [offset_bw_bands_filter_1, offset_bw_bands_filter_2, offset_bw_bands_filter_3, offset_bw_bands_filter_4],
            [
                (offset_first_fil_in, offset_first_fil_out),
                (offset_second_fil_in, offset_second_fil_out),
                (offset_third_fil_in, offset_third_fil_out),
                (offset_fourth_fil_in, offset_fourth_fil_out)
            ],
            [
                offset_lo_3_3, offset_lo_3_4, offset_lo_2_3, offset_lo_2_4,
                offset_lo_0_4, offset_lo_0_3, offset_lo_1_4, offset_lo_1_3,
                offset_lo_11_4, offset_lo_11_3
            ]
        )

    def _write_custom_hex_coe_scipy(self, addr: int, offset: int, padding: int = 4, number_of_channel: int = 2,
                                    offset_bw_bands: int = 5000000) -> None:
        fs = 20000000  # Sample rate, Hz  TODO 12_000_000
        numtaps = 420  # Size of the FIR filter # TODO 432
        if number_of_channel > 0:
            edges, desired = self.filter_configuration(number_of_channel, offset_bw_bands, fs)

            taps = remez(numtaps=numtaps, bands=edges, desired=desired, Hz=fs)

            value = list(map(self.convert, taps))
            value = [0 for _ in range(padding)] + value[:] + [0 for _ in range(padding)]
            value = value[:math.ceil(len(value) / 2)]
            value = self.reverse_group(value[::-1], 15) # TODO 24
        else:
            value = [0] * (numtaps // 2 + padding)

        if not settings.DEBUG:
            self.reset_coe_controller(addr, offset)
            time.sleep(0.001)
            mem = DevMem(address=addr, number_of_words=32, word_size=2)
            for idx, c in enumerate(value):
                try:
                    mem.write(offset=offset, value=c)
                except Exception as e:
                    raise e

    def _write_custom_hex_coe(self, addr: int, offset: int, value: List[int], padding: int = 4) -> None:
        value = value[:math.ceil(len(value) / 2)]
        value = self.reverse_group(value[::-1], 5)
        if not settings.DEBUG:
            self.reset_coe_controller(addr, offset)
            time.sleep(0.001)
            mem = DevMem(address=addr, number_of_words=100, word_size=2)
            for idx, c in enumerate(value):
                try:
                    mem.write(offset=offset, value=c)
                    #print('{:x}'.format(mem.read(offset=offset).get_value()))
                except Exception as e:
                    raise e

    @staticmethod
    def reverse_group(inputed: List[float], k: int) -> list:
        start = 0
        result = []
        while start < len(inputed):
            if len(inputed[start:]) < k:
                result = result + list(reversed(inputed[start:]))
                break

            result = result + list(reversed(inputed[start:start + k]))
            start = start + k
        return result

    def _write_custom_coe(self, addr: int, offset: int, value: conlist(int), padding: int = 0) -> None:
        self.reset_coe_controller(addr, offset)
        time.sleep(0.001)
        value = ([0] * padding) + value + ([0] * padding)
        value = value[:math.ceil(len(value) / 2)]
        value = self.reverse_group(value[::-1], 5)

        reg = FXP(signed=True,
                  enableOverflowStatus=False,
                  word_length=16,
                  integer_word_length=1)

        if not settings.DEBUG:
            mem = DevMem(address=addr, number_of_words=32, word_size=2)
            for idx, c in enumerate(value):
                try:
                    packed = reg.pack_data(c, 0)
                    ba = BitArray(format(packed, '#018b'))
                    val = int(ba.bin, 2)
                    mem.write(offset=offset, value=val)
                except Exception as e:
                    raise e
        else:
            for c in value:
                packed = reg.pack_data(c, 0)
                ba = BitArray(format(packed, '#018b'))
                val = int(ba.bin, 2)

    def _write_typical_coe_scipy(self, addr: int, offset: int) -> None:
        self._write_custom_hex_coe(addr, offset, [], padding=0)

    def _write_typical_coe(self, addr: int, offset: int, coe_name: str) -> None:
        value = self.coe_files.get(coe_name, [])
        self._write_custom_hex_coe(addr, offset, value, padding=0)

    def set_delays(self, delays: Delays) -> None:
        if not settings.DEBUG:
            mem = DevMem(address=Dsp.UPLINK_SV_RX_BASE_ADDR, number_of_words=128, word_size=2)
            for i, value in enumerate(self.DELAY_OFFSET):
                mem.write(offset=self.DELAY_OFFSET[i], value=Dsp.calc(delays.sv_rx_delay[i]))
            mem = DevMem(address=self.DOWNLINK_BASE_ADDR, number_of_words=128, word_size=2)
            for i, value in enumerate(self.DELAY_OFFSET):
                mem.write(offset=self.DELAY_OFFSET[i], value=Dsp.calc(delays.sv_tx_delay[i]))

    @staticmethod
    def calc(delay) -> int:
        return round(delay * 1000 / 33)
